home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / PGM_TOOL / TCUNIT / STATIC.TXT < prev    next >
Text File  |  1989-07-02  |  15KB  |  345 lines

  1. Article text, originally published as "Demagnetizing Static
  2. Variables," Programmer's Journal 7.1, 1989, pp 42-52.
  3.  
  4. -----
  5. Copyright (c) 1989 by Tom Swan. All rights reserved. Limited
  6. permission granted to use programming in compiled form only.
  7. -----
  8.  
  9. Author's note: This information and the accompanying programs are 
  10. compatible with Turbo Pascal 5.5. I wrote the article before this
  11. version was available, so any references to page numbers in the
  12. Borland references may be incorrect.
  13.  
  14.  
  15.  
  16.  
  17.                  Demagnetizing Static Variables
  18.  
  19.                            by Tom Swan 
  20.  
  21.  
  22.  
  23. "Modifying typed constants (alias static variables) stored in 
  24. Turbo Pascal 4.0 and 5.0 compiled EXE code files is hairy 
  25. business--but it can be done if you're careful."
  26.  
  27.  
  28. Static variables, or typed constants as Turbo Pascal calls them, 
  29. stick to compiled code like magnets to iron.  Declared as 
  30. constants but compiled as variables, typed constants are stored 
  31. on disk inside a program's EXE code file.  Because typed 
  32. constants have predefined values, they eliminate wasteful 
  33. initializations of global variables at run time.  Despite this 
  34. advantage, however, after compiling, there's no easy way to 
  35. change a program's typed constants--for example, to let people 
  36. make keyboard assignments, select window colors, and modify other 
  37. default settings.
  38.      A telephone call from a reader prompted me to think of 
  39. several possible solutions to this problem.  I knew I'd have to 
  40. locate the typed constants embedded in the compiled code file and 
  41. modify only the necessary bytes, storing the result back on disk 
  42. with all other bytes intact.  Mucking around with EXE disk files 
  43. gives me the heebie jeebies, so the code would have to be 
  44. reasonably immune to changes in future compiler versions.  The 
  45. program should run fast, be easy to modify, work with both small 
  46. and large applications, wash the floor, dust the shelves, and 
  47. take out the garbage.
  48.      The following describes the results of my experiments, 
  49. largely successful although I'm still doing the floor, shelves, 
  50. and garbage by hand.  After a brief refresher course on typed 
  51. constants as stored on disk and in memory, I'll explain how to 
  52. use the accompanying library unit to write custom installation 
  53. programs.  Also included are demonstration programs that you can 
  54. use as starting places for your own projects.
  55.  
  56.  
  57. A Needle in a Byte Stack
  58.  
  59. Finding typed constants in memory and on disk isn't nearly as 
  60. difficult as finding a needle in you know what.  Let's start with 
  61. values in memory, as these are the easiest to locate.  The first 
  62. typed constant in memory is always stored at offset zero in the 
  63. data segment.  (There is only one data segment in a Turbo Pascal 
  64. program.)  Suppose you have these declarations:
  65.  
  66. const
  67.    aword : word = ( 1024 );
  68.    achar : char = ( 'X' );
  69.    abyte : byte = ( 15 );
  70.  
  71.      The typed constant aword is stored at DSeg:0000, occupying 
  72. two bytes.  After this comes achar at DSeg:0002.  Next, abyte 
  73. follows at DSeg:0003.  DSeg is a predeclared constant equal to 
  74. the value of register DS.  Rather than calculate these offsets 
  75. manually, use the Ofs function to find the address of any typed 
  76. constant.  For example, this displays the offset addresses of the 
  77. three typed constants in memory:
  78.  
  79. writeln( 
  80.   ' aword=', ofs(aword),
  81.   ' achar=', ofs(achar),
  82.   ' abyte=', ofs(abyte) );
  83.  
  84.      In Turbo Pascal 4.0, typed constant values follow each other 
  85. with no wasted bytes between.  As a result, lone byte values can 
  86. lead to extra CPU machine cycles by forcing words to begin at odd 
  87. addresses.  If you insert a fourth typed constant word between 
  88. achar and abyte, the new value resides at DSeg:0003, making the 
  89. CPU work harder to access the value.  To fix this problem, Turbo 
  90. Pascal 5.0 provides a new switch {$A+} to align typed constants 
  91. and other variables on even addresses, letting the CPU access 16-
  92. bit quantities as quickly as possible.  (Word alignment offers no 
  93. advantage on PCs with 8088 processors.)  The default is {$A-} for 
  94. no alignment.
  95.  
  96.  
  97. We Are Not Alone
  98.  
  99. Your typed constants are not alone in memory.  The System unit, 
  100. containing Turbo Pascal's runtime library and linked into every 
  101. compiled program, declares its own set of typed constants.  (The 
  102. 5.0 Reference Guide lists these on page 125.)  In memory, items 
  103. in the data segment follow this order:
  104.  
  105. 1. Program typed constants
  106. 2. Unit typed constants
  107. 3. System typed constants
  108. 4. Global variables
  109.  
  110.      The main program's typed constants are always first, 
  111. beginning at offset zero.  Next come any typed constants declared 
  112. in units that the program lists in a USES declaration.  Last come 
  113. the System unit's typed constants, followed by the program's 
  114. global variables.  Together, these four items make up the 
  115. program's data segment, which can be as large as 64K.  Items 1-3 
  116. are stored in the compiled EXE code file.  Memory for global 
  117. variables (item 4) is allocated at run time--global variables are 
  118. never stored in the disk code file.
  119.  
  120.  
  121. Smart Linker; Dumb Programmer
  122.  
  123. Turbo Pascal's smart and crafty linker, which attempts to 
  124. optimize compiled code, requires you to refer to at least one 
  125. typed constant declared in your program and in all units.  If you 
  126. don't, the linker realizes you aren't using the typed constants 
  127. and does not reserve space for them in the code!  While writing 
  128. codeless test programs to determine where typed constants are 
  129. stored, it took me longer than I care to reveal to realize the 
  130. linker was "helping" me by throwing my constants away.  The 
  131. following short program demonstrates how Turbo's smart aleck 
  132. linker can get you into trouble as it did me:
  133.  
  134. program test;
  135. const 
  136.    atc:char=('a');
  137. begin
  138.    writeln( atc );
  139.    writeln( 
  140.     char( ptr(dseg,0 )^) )
  141. end.
  142.  
  143.      The program declares one typed constant, atc.  Two writeln 
  144. statements display atc's value, the letter 'a'.  The first 
  145. writeln refers to atc by name.  The second refers to atc the hard 
  146. way, dereferencing a pointer to DSeg:0000 and recasting as type 
  147. Char, admittedly the long way around a simple job.  Take out the 
  148. first writeln statement and guess what happens.  The second 
  149. writeln no longer displays 'a'!  Turbo Pascal throws out atc 
  150. because it doesn't realize that the ptr expression refers to the 
  151. typed constant.
  152.      Obviously, you won't usually refer to typed constants this 
  153. way, but you might write a program that expects another module to 
  154. reference a typed-constant array or large record at DSeg:0000.  
  155. Don't do this.  Turbo Pascal does not save typed constants unless 
  156. you refer by name to at least one in each module.  The same rule 
  157. goes for typed constants in units.  Either the unit code or 
  158. another module must refer to at least one typed constant by name 
  159. or the linker throws these babies out with the bathwater.
  160.  
  161.  
  162. Sniffing for Typed Constants
  163.  
  164. Turbo Pascal stores typed constants as the last bytes in a disk 
  165. code file.  At run time, the bytes are copied to the start of the 
  166. program's data segment in memory.  On disk and in memory, the 
  167. bytes are paragraph aligned--that is, beginning at an offset in 
  168. the code file and at an address in memory evenly divisible by 16.
  169.      The problem of writing an installation program to modify 
  170. default values in compiled disk code files requires finding the 
  171. offset to the first byte of the first typed constant.  Listing 1 
  172. (WINDGLOB.PAS) makes this easy by declaring a marker CBase 
  173. assigned the string value '@CBASE@'.  The program's default 
  174. settings follow CBase--in this sample, the typed constants 
  175. WBForeColor to WTitle.  Last, a dummy typed constant EBase marks 
  176. the end of the typed constants area.  Ebase's data type and value 
  177. are unimportant, but CBase must be a string.
  178.      Because the application and installation program refer to 
  179. the same values, it's best to declare all typed constants in a 
  180. separate unit similar to WINDGLOB.PAS.  In this example, the unit 
  181. has no code, although it certainly could.
  182.      The next step is to write the main application, WINDTEST.PAS 
  183. in Listing 2.  This program lists WINDGLOB in a USES declaration, 
  184. importing the typed constants from Listing 1.  Run WINDTEST to 
  185. display a simple window with scrolling random characters.  Press 
  186. any key to end the program.
  187.      The last job is to write the installation program, which 
  188. modifies the typed constant default values in the compiled 
  189. WINDTEST.EXE program.  Listing 3 (WINDINST.PAS) displays a menu 
  190. to let you change window colors and type a new window title 
  191. string.  The next section describes how to compile and run the 
  192. programs.
  193.  
  194.  
  195. Compiling The Test Programs
  196.  
  197. First compile Listing 4, TCUNIT.PAS, creating TCUNIT.TPU.  (With 
  198. the integrated Turbo Pascal compiler, be sure the Compile menu's 
  199. Destination option is set to "Disk.")  TCUNIT has procedures to 
  200. help you write your own installation programs and contains a 
  201. function to locate and modify typed constant values in compiled 
  202. EXE files.  I'll explain how to use the unit in a moment.
  203.      After compiling TCUNIT, compile WINDGLOB.PAS to 
  204. WINDGLOB.TPU.  Then, compile WINDTEST.PAS and run the test 
  205. application.  Finally, compile WINDINST.PAS and run.  Change any 
  206. of the values listed in the menu.  In this no-frills version, you 
  207. have to specify colors by number even though WINDGLOB.PAS 
  208. declares the default colors by name, using identifiers declared 
  209. in the standard Crt unit.  Press Q to quit and modify 
  210. WINDTEST.EXE.  Press X to quit and not save changes.  Run 
  211. WINDTEST again to see the effect of your new values.
  212.  
  213.  
  214. How TCUnit Works
  215.  
  216. Two procedures in TCUNIT, GetWord and GetStr, prompt for word and 
  217. string values.  WINDINST calls these procedures to let you enter 
  218. new values for various typed constant values.  You'll probably 
  219. want to add your own procedures to prompt for values of other 
  220. types: such as GetInteger, GetChar, GetReal, and so forth.  Use 
  221. the two procedures listed here as guides.
  222.      Function ChangesSaved in TCUNIT does all the work of opening 
  223. the compiled EXE code file, locating the typed constants, and 
  224. writing the modified values back to disk.  See the end of 
  225. WINDINST for an example call to this function, which requires a 
  226. file name, the CBase marker string, and the offset addresses of 
  227. the CBase and EBase typed constants.  ChangesSaved performs 
  228. several operations:
  229.  
  230. * First the EXE file is opened.  Parameter fileName must be the 
  231. name of a compiled EXE file with CBase and EBase typed constants 
  232. around the values to be modified, as shown in WINDGLOB.PAS.  
  233.  
  234. * Next, function FoundCBase returns true if the CBase marker 
  235. string is found in the EXE code file.  If so, FoundCBase returns 
  236. the byte offset to this string.  If not, FoundCBase returns 
  237. false, causing an error message to be displayed.
  238.  
  239. * If CBase is found, procedure SaveData copies the in-memory 
  240. typed constants to the compiled EXE code file, overwriting only 
  241. the bytes from CBase to the byte just before EBase.  No other 
  242. bytes are changed in the disk code file.
  243.  
  244.      For all this to work correctly, you must be sure to use the 
  245. identical typed constants in both the main (WINDTEST) and 
  246. installation (WINDINST) programs.  The safest bet is to declare 
  247. all typed constants in one separate unit (WINDGLOB) and then use 
  248. the unit in both programs.
  249.  
  250.  
  251. Writing Your Own Installers
  252.  
  253. Even without fully understanding every detail about how TCUnit 
  254. operates, it's easy to write your own installation programs.  
  255. Just follow these steps:
  256.  
  257. 1. You don't have to modify TCUnit, although you might want to 
  258. add procedures to prompt for specific data types as mentioned 
  259. earlier.
  260.  
  261. 2. Store all typed constants in a separate unit and compile.  
  262. Start with CBase and end with EBase as in WINDGLOB.PAS.  The 
  263. number, type, names, and values of typed constants in between 
  264. CBase and EBase are completely up to you.
  265.  
  266. 3. Write your application, using the unit of typed constants from 
  267. step 2.  Compile to disk.
  268.  
  269. 4. Write your installation program, using WINDINST.PAS as a 
  270. guide.  Add TCUNIT and your typed constants unit to the program's 
  271. USES statement.  Call ChangesSaved to write modified values to 
  272. your program's code file.
  273.  
  274.      By the way, you can also use WINDINST to modify its own code 
  275. file, WINDINST.EXE.  Doing this displays the modified settings 
  276. the next time you run the installation program.  Also, the CBase 
  277. marker string (see WINDGLOB.PAS) can be anything you like--it 
  278. doesn't have to be '@CBASE@' as it is here.  The string must be 
  279. unique, occurring nowhere else in the code file.  You might 
  280. change CBase to your name or copyright.  Then, if someone tries 
  281. to remove your notice from the code file, the installation 
  282. program will no longer work.  Sneaky, no?
  283.  
  284.  
  285. Improving the Programs
  286.  
  287. When you run these tests, you'll notice that WINDINST pauses for 
  288. several seconds while hunting for CBase in the compiled code.  
  289. There's a simple way to reduce this time.  First, remove the (* 
  290. and *) comment brackets from the two writeln statements inside 
  291. TCUNIT's FoundCBase function.  Recompile WINDINST and press Q.  
  292. Jot down the reported offset.  When I did this, I got 4768, 
  293. although your number is likely to be different.  The value is 
  294. decimal, not hex.
  295.      Edit TCUNIT.PAS again and replace the comment brackets in 
  296. FoundCBase.  Then change the double IF statement in ChangesSaved 
  297. near the end of the program to read:
  298.  
  299. IF err=0 THEN
  300.  SaveData(f,4768,...)
  301.  
  302.      In other words, replace variable offset with the value you 
  303. noted earlier and remove the call to FoundCBase.  Now when you 
  304. compile and run WINDINST, the disk update takes place almost 
  305. instantaneously as TCUNIT no longer has to hunt for the offset to 
  306. CBase.
  307.      Perform this step only after compiling your main program for 
  308. the last time.  (Is there ever a last time?)  Any changes to your 
  309. typed constants or to the program require you to recalculate the 
  310. offset in the code file.  Using the wrong offset is almost sure 
  311. to lead to a crash.
  312.      Although these ideas and test programs are experimental, I'm 
  313. planning to use them as the basis for an installation program in 
  314. my Turbo Pascal database system as well as in other programs.  In 
  315. the past, I've found many uses for typed constants, but I'm 
  316. planning to work them in more frequently, especially now that I 
  317. have a way to demagnetize their previously all too static values.
  318.  
  319.  
  320. Listing 1: WINDGLOB.PAS
  321. Listing 2: WINDTEST.PAS
  322. Listing 3: WINDINST.PAS
  323. Listing 4: TCUNIT.PAS
  324.  
  325.                                ###
  326.  
  327.  
  328. About the author...
  329.  
  330. Tom Swan writes the monthly PC World columns Star-Dot-Star and
  331. Developer's Toolbox.  He is a contributing editor to PC World and 
  332. Programmer's Journal, and is the author of Mastering Turbo Assembler
  333. (1989) and Mastering Turbo Pascal 5.5 (Sep 1989) published by Howard
  334. W. Sams, Indianapolis.  You can contact the author at:
  335.  
  336. Swan Software
  337. P. O. Box 206
  338. Lititz, PA 17543
  339. (717) 627-1911
  340.  
  341. Compuserve ID: 73627,3241
  342. MCI Mail handle: TSWAN
  343.   
  344.  
  345.